Rust FFI
RustとC/C++間でのやりとり。
externブロックにCの静的あるいは共有ライブラリの関数シグネチャを呼び出すことができる。
linkアトリビュートによりリンカーにライブラリのリンクを指示。
libsnappy.so -> #link(name = "snappy")
dynamic - #[link(name = "readline)
static - #[link(name = "my_build_dependency", kind = "static")]
ライブラリ形式での配布が不要
libfoo.aにアーカイブ
framework - #[link(name = "CoreFoundation", kind = "framework")]
OSXターゲットでのみ利用可能
code:ffi.rs
extern crate libc;
use libc::{c_int, size_t};
extern {
fn snappy_compress(input: *const u8,
input_length: size_t,
compressed: *mut u8,
compressed_length: *mut size_t) -> c_int;
fn snappy_uncompress(compressed: *const u8,
compressed_length: size_t,
uncompressed: *mut u8,
uncompressed_length: *mut size_t) -> c_int;
fn snappy_max_compressed_length(source_length: size_t) -> size_t;
fn snappy_uncompressed_length(compressed: *const u8,
compressed_length: size_t,
result: *mut size_t) -> c_int;
fn snappy_validate_compressed_buffer(compressed: *const u8,
compressed_length: size_t) -> c_int;
}
unsafeブロック内で使用
code:ffi.rs
fn main() {
let x = unsafe { snappy_max_compressed_length(100) };
println!("max compressed length of a 100 byte buffer: {}", x);
}
以下のC headerを
code:doggo.h
typedef struct Doggo {
int many;
char wow;
} Doggo;
void eleven_out_of_ten_majestic_af(Doggo* pupper);
以下のrust ffiに変換
#repr(C)によりCのメモリレイアウトを利用できる
code:doggo.rs
pub struct Doggo {
pub many: ::std::os::raw::c_int,
pub wow: ::std::os::raw::c_char,
}
extern "C" {
pub fn eleven_out_of_ten_majestic_af(pupper: *mut Doggo);
}
useful libraries for rust ffi
libc - Raw FFI bindings to platform libraries like libc.
cc-rsによりbuild.rsでcをビルド可能
bindgenによりFFI codeの自動生成が可能
Debugging a rust bindgen issue
include path problem of bindgen
cmake-rs
About static libraries path linking with a rust program
cargoで任意の共有ライブラリに対する参照
code:build.rs
fn main() {
println!("cargo:rustc-link-search=native=/hoge/fuga/bar/foo");
}
Call C++ from Rust
To call C functions in Rust, you just have to wrap them with extern, do some basic type casting and sometimes unsafe.
To call C++ functions, since Rust does not have built-in knowledge of C++ features, you may have to do a lot of manual translation.
namespace必要
linking
Put the library into your system library searching paths like /usr/lib or /usr/local/lib/, make sure it can be found by ldconfig -p.
Or use the environment variable LD_LIBRARY_PATH to specify the path where your library lays when you run cargo from the CLI.
cc-rs
exmaples of rust ffi
std::vector
buffer start と lengthが必要
buffer start : [T]::as_mut_ptrで*mut T型が得られる。
rust -> cにvecを渡すパターン
CコードにVecを借用する場合
code: borrow.rs
extern "C" {
fn some_c_function(ptr: *mut i32, len: ffi::size_t);
}
fn safe_wrapper(a: &mut i32) { unsafe {
some_c_function(a.as_mut_ptr(), a.len() as ffi::size_t);
}
}
Cコードに所有権を渡したい場合
code: pass_ownership.rs
use std::mem;
extern "C" {
fn c_sink(ptr: *mut i32, len: ffi::size_t);
}
fn sink_wrapper(mut vec: Vec<i32>) {
vec.shrink_to_fit();
assert!(vec.len() == vec.capacity());
let ptr = vec.as_mut_ptr();
let len = vec.len();
mem::forget(vec); // prevent deallocation in Rust
// The array is still there but no Rust object
// feels responsible. We only have ptr/len now
// to reach it.
unsafe {
c_sink(ptr, len as ffi::size_t);
}
}
c -> rustにvecを渡すパターン
buffer start: *mut i32
length: i32
をcから得る。
pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]
でsliceにする。
pub fn to_vec(&self) -> Vec<T>
でvecへ。
c++側でポインタとlengthの情報からstd::vectorを生成
まめ
rustからCへの所有権の移動を考える。
c++でのstd::unique_ptrはBox
c++での.release()はBox::into_raw
code:pass_ownership.rs
let c = Box::new(SomeStruct::new());
unsafe {
c_function(Box::into_raw(c));
}
C関数はRustへ所有権を返却しなければならない。
code:destroy.rs
pub unsafe extern "C" fn delete_some_struct(ptr: *mut SomeStruct) {
// Convert the pointer back into a Box and drop the Box.
Box::from_raw(ptr);
}
rustのstructをCに渡す
rustで定義した以下の関数をcで呼び出してポインタを操作できるようにして実質的にstructを渡しているような方法を考える。
以下のように生ポインタを返すとmake_mystruct()を抜ける時にMyStructの所有権を持つsが解放されるので生ポインタがダングリングポインタになってしまい実行時エラーになる。(コンパイラは生ポインタに関連するライフタイム分析は行わないのでコンパイルは通る)
code:main.rs
pub extern "C" fn make_mystruct() -> *mut MyStruct {
let mut s = MyStruct::new();
&mut s as *mut MyStruct
}
structをBox化し、ヒープに置き、所有権とライフタイムが追跡されるBoxポインタを生ポインタに変換することで追跡を回避しmake_mystructを抜けた後もstructの削除を回避する。
Box::into_raw()は内部的にstd::mem::transmute()が呼び出されて強制的な型変換(変換前後のデータサイズは等しい)が行われ、Box<MyStruct>から*mut MyStructの変換を行なっている。
code:main.rs
pub extern "C" fn make_mystruct() -> *mut MyStruct {
let s = Box::new(MyStruct::new());
Box::into_raw(s)
}
上記とは逆で、Box::from_rawメソッドで生ポインタからBox型に戻し消費する。
code:main.rs
pub extern "C" fn destroy_mystruct(p: *mut MyStruct) {
unsafe { Box::from_raw(p) };
}
vecパターン
code:main.rs
unsafe fn alloc(len: usize) -> *mut u8 {
let mut vec = Vec::<u8>::with_capacity(len);
vec.set_len(len);
Box::into_raw(vec.into_boxed_slice()) as *mut u8
}
unsafe fn free(raw: *mut u8, len : usize) {
let s = std::slice::from_raw_parts_mut(raw, len);
let _ = Box::from_raw(s);
}
Option<ポインタ型> の None は null ポインタになる
rustで動的にバッファを確保する方法
references